package c15IO;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 自定义序列化时实现Serializable方式与实现Externalizable方式的异同：
 *
 * @author 炜sama
 * 相同点：
 * 1、读写方法体代码基本相同。
 * 2、都可以有writeReplace和readResolve方法。
 * 不同点：
 * 1、后者必须提供无参构造方法！不然反序列化时有异常！
 * 2、前者默认情况下系统自动储存必要信息；后者必须实现相应方法，决定储存哪些信息。
 * 3、前者性能略差，后者略高。
 */
public class 自定义序列化之Serializable {

	public static void main(String[] args) {
		String fn = "per.ser";
		ObjectInputStream ois = null;
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream(fn));
			PersonSer p = new PersonSer("asdfg", 21);
			oos.writeObject(p);
			ois = new ObjectInputStream(new FileInputStream(fn));
			System.err.println(ois.readObject());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

//没有实现Serializable的父类
class P {
	//父类必须提供无参构造器！如果没有定义有参构造器，可以不用显式定义无参构造器，
	//因为系统会自动定义一个无参构造器！去掉这个无参构造器，下面反序列化时会抛异常！
	public P() {
	}

	public P(String name, int age) {
	}
}

//因为父类没有实现Serializable接口，所以这里必须实现
class Person extends P implements Serializable {
	//为简单测试起见，属性定义为可见的
	protected String name;
	protected int age;

	public Person(String name, int age) {
		super(name, age);
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		//用反射来取得类名，子类就不用重新这个方法了！
		return this.getClass().getSimpleName() + "[name=" + name + ", age=" + age + "]";
	}
}

//因为父类Person已经实现Serializable接口，所以子类PersonSer就不用再次实现该接口了！
class PersonSer extends Person {
	private static final long serialVersionUID = 1L;

	public PersonSer(String name, int age) {
		super(name, age);
	}

	//@1只能是private！否则抛异常！默认情况下，该方法会调用oos.defaultWriteObject();
	//writeObject方法负责写入特定类的实例的状态，以便相应的readObject方法可以恢复它。
	//通过重写该方法，程序员可以完全获得对序列化机制的控制，程序员可以自主决定哪些属性需要序列化，需要怎样序列化。
	private void writeObject(ObjectOutputStream oos) throws IOException {
		System.err.println("writeObject");
		System.err.println("writeObject [name=" + name + ", age=" + age + "]");
		oos.writeObject(new StringBuffer(name).reverse());
		oos.writeInt(age);
	}

	//@2只能是private！否则抛异常！默认情况下，该方法会调用ois.defaultReadObject();
	//readObject方法负责从流中读取并恢复对象属性，通过重写该方法，
	//程序员可以完全获得对反序列化机制的控制，可以自主决定需要反序列化哪些属性，以及进行怎样的反序列化。
	private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
		System.err.println("readObject");
		this.name = ((StringBuffer) ois.readObject()).reverse().toString();
		this.age = ois.readInt();
	}

	//@3用来正确地初始化反序列化的对象。如在：1、接收方使用的反序列化类的版本不同于发送方；
	//2、或者接收者版本扩展的类不是发送者版式本扩展的类时；3、序列化流被篡改时。
	//但是在Java1.7环境下测试发现此方法不会被调用！
	private void readObjectNoData() throws ObjectStreamException {
		System.err.println("readObjectNoData");
		this.name = "默认值";
		this.age = -1;
	}

//	//@4额外方法。用返回的对象o替换writeObject方法写入对象。
//	//注意：如果o中也有writeReplace方法，则有两种情况：
//	//1、若o与该方法调用者同属一个类，则调用writeObject，并以o的属性值替换当前对象的属性值。(如下面@@1)
//	//2、否则，若o中还有writeReplace方法，则继续调用。若其返回的对象还有writeReplace方法，则一直调用下去，
//	//		直到没有writeReplace方法(如下面@@0)或者碰到1、的情况为止(如下面@@2)。
//	private Object writeReplace()  throws ObjectStreamException {//可以是任意访问控制符，但是建议使用private！
//		System.err.println("writeReplace");
////		return "writeReplace()：writeReplace";//@@0
//		return new PersonSer("writeReplace", -1);//@@1
//		
////		return new PersonSer2("writeReplace", -1);//@@2。
//		//PersonSer2的writeReplace方法返回的是PersonSer(见下面@@3)，序列化时进入了死循环！！
//	}
//	
//	//@5额外方法。这个方法在readObject方法后被调用，返回值替换原来反序列化的值！
//	//以免被子类继承使得子类在未重写的情况下返回的是父类对象！
//	//所有单例类、枚举类在实例化时都应该提供readResolve方法，可以保证反序列化的对象依然正确！
//	protected Object readResolve()  throws ObjectStreamException {//可以是任意访问控制符，但是建议使用private！
//		System.err.println("readResolve");
//		return new PersonSer("王尼玛", 99);
//	}

}

//同上
class PersonSer2 extends PersonSer {
	private static final long serialVersionUID = 1L;

	public PersonSer2(String name, int age) {
		super(name, age);
	}

	private void writeObject(ObjectOutputStream oos) throws IOException {
		System.err.println("2writeObject");
		System.err.println("2writeObject [name=" + name + ", age=" + age + "]");
		oos.writeObject(new StringBuffer(name).reverse());
		oos.writeInt(age);
	}

	private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
		System.err.println("2readObject");
		this.name = ((StringBuffer) ois.readObject()).reverse().toString();
		this.age = ois.readInt();
	}

	private Object writeReplace() throws ObjectStreamException {
		System.err.println("2writeReplace");
		return new PersonSer("writeReplace", -1);//@@3
	}
}